package generator

import (
	"kumachan/interpreter/lang/common/name"
	"kumachan/interpreter/lang/common/source"
	"kumachan/interpreter/compiler/checker/checked"
	"kumachan/stdlib"
	rt "kumachan/interpreter/runtime/def"
	ch "kumachan/interpreter/compiler/checker"
)


func Compile(p *ch.Program, res source.ResourceIndex) (*rt.Program, source.Errors) {
	var makeProgramMetadata = func(p *ch.Program) rt.ProgramMetadata {
		return rt.ProgramMetadata {
			EntryModulePath: p.EntryPath,
		}
	}
	var adaptDispatchMapping = func(p *ch.Program) rt.DispatchTableDefMapping {
		var getId = func(table *checked.DispatchTable) rt.DispatchTableIdentifier {
			return rt.DispatchTableIdentifier {
				ConcreteTypeId:  typeNameToSymbol(table.Concrete),
				InterfaceTypeId: typeNameToSymbol(table.Interface),
			}
		}
		var m = make(rt.DispatchTableDefMapping)
		for _, table := range p.DispatchMapping {
			var id = getId(table)
			var methods = make([] rt.MethodDef, len(table.Methods))
			for i, item_ := range table.Methods {
				var method = (func() rt.MethodDef {
					switch item := item_.(type) {
					case checked.MethodFunction:
						return rt.MethodDef {
							Function: functionNameToSymbol(item.Name),
						}
					case checked.MethodField:
						return rt.MethodDef {
							Field: rt.EnsureShortIndex(item.Index),
						}
					default:
						panic("impossible branch")
					}
				})()
				methods[i] = method
			}
			var included = make([] rt.DispatchTableIdentifier, len(table.Included))
			for i, item := range table.Included {
				included[i] = getId(item)
			}
			m[id] = rt.DispatchTableDef {
				Methods:  methods,
				Included: included,
			}
		}
		return m
	}
	var genRpcConf = func(p *ch.Program) rt.RpcConf {
		var sch = p.RpcInfo.SchemaTable
		var srv = p.RpcInfo.ServiceIndex
		var utl = p.RpcInfo.KmdUtilFunctionsInfo
		var validators = make(rt.KmdValidatorTable)
		var adapters = make(rt.KmdAdapterTable)
		for id, item := range utl.KmdValidatorIndex {
			validators[id] = rt.KmdValidatorInfo {
				Symbol: functionNameToSymbol(item),
			}
		}
		for id, item := range utl.KmdAdapterIndex {
			adapters[id] = rt.KmdAdapterInfo {
				Symbol: functionNameToSymbol(item),
			}
		}
		var kmd_info = rt.KmdConf {
			SchemaTable:       sch,
			KmdAdapterTable:   adapters,
			KmdValidatorTable: validators,
		}
		var sim = p.RpcInfo.ServiceInfoMapping
		var services_info = make(rt.RpcServiceInfoIndex)
		for _, item := range sim {
			var id = item.Identifier
			var interface_, exists = srv[id]
			if !(exists) { panic("something went wrong") }
			services_info[id] = rt.RpcServiceInfo {
				Interface:   interface_,
				MethodIndex: item.MethodIndex,
			}
		}
		return rt.RpcConf {
			KmdConf: kmd_info,
			RpcServiceInfoIndex: services_info,
		}
	}
	var compileFunctions = func(p *ch.Program) ([] rt.FunctionSeed, *GlobalStatistics, ([] func()(uint,rt.Symbol)), ([] uint)) {
		var g = NewGlobalStatistics()
		var seeds = make([] rt.FunctionSeed, len(p.Functions))
		var thunks = make([] func()(uint,rt.Symbol), 0)
		var effects = make([] uint, 0)
		for i, f := range p.Functions {
			seeds[i] = compileFunction(f, g)
			var index = uint(i)
			if f.IsThunk {
				var sym = functionNameToSymbol(f.Name)
				thunks = append(thunks, func() (uint, rt.Symbol) {
					return index, sym
				})
			}
			if f.IsEffect {
				effects = append(effects, index)
			}
		}
		return seeds, g, thunks, effects
	}
	var processStatistics = func(p *ch.Program, g *GlobalStatistics, thunks ([] func()(uint,rt.Symbol))) ([] uint, source.Errors) {
		var errs source.Errors
		for b := range g.UnusedBindings {
			source.ErrorsJoin(&errs, source.MakeError(b.Location,
				E_UnusedBinding {
					Name: b.Name,
				}))
		}
		var index = make(map[rt.Symbol] uint)
		for _, pair := range thunks {
			var x_index, x = pair()
			index[x] = x_index
		}
		var deps = g.DependencyGraph
		var thunk_seq = make([] uint, 0)
		var q = make([] rt.Symbol, 0)
		var dep_quantity = make(map[rt.Symbol] uint)
		for _, pair := range thunks {
			var _, x = pair()
			for y := range deps[x] {
				var _, y_is_thunk = index[y]
				if y_is_thunk {
					dep_quantity[x] ++
				}
			}
			if dep_quantity[x] == 0 {
				q = append(q, x)
			}
		}
		for len(q) > 0 {
			var x = q[0]
			q = q[1:]
			thunk_seq = append(thunk_seq, index[x])
			for _, pair := range thunks {
				var _, y = pair()
				var y_deps = deps[y]
				var _, y_deps_x = y_deps[x]
				if y_deps_x {
					dep_quantity[y] --
					if dep_quantity[y] == 0 {
						q = append(q, y)
					}
				}
			}
		}
		var bad = make([] rt.Symbol, 0)
		for _, pair := range thunks {
			var _, x = pair()
			if dep_quantity[x] > 0 {
				bad = append(bad, x)
			}
		}
		if len(bad) > 0 {
			var names = make([] string, len(bad))
			for i, item := range bad {
				names[i] = item.MessageString()
			}
			var loc = p.Functions[index[bad[0]]].Location
			var err = source.MakeError(loc,
				E_CircularThunkDependency {
					Names: names,
				})
			source.ErrorsJoin(&errs, err)
		}
		if errs != nil { return nil, errs }
		return thunk_seq, nil
	}
	var functions, g, thunks, effects = compileFunctions(p)
	var thunk_seq, errs = processStatistics(p, g, thunks)
	if errs != nil { return nil, errs }
	var metadata = makeProgramMetadata(p)
	var dispatch_def = adaptDispatchMapping(p)
	var rpc_conf = genRpcConf(p)
	return &rt.Program {
		Metadata:    metadata,
		Functions:   functions,
		ThunkSeq:    thunk_seq,
		Effects:     effects,
		DispatchDef: dispatch_def,
		RpcConf:     rpc_conf,
		Resources:   res,
	}, nil
}

func CompileStandaloneExpr(expr *checked.Expr, info rt.FunctionInfo) (rt.FunctionSeed, ([] *checked.LocalBinding)) {
	var input = checked.ProductPatternInfo(nil)
	var s = new(Statistics)
	var ctx = MakeFunctionExprContext(input, nil, s)
	compileExpr(expr, ctx)
	return ctx.CollectEverything(info)
}

func compileFunction(f ch.ProgramFunction, g *GlobalStatistics) rt.FunctionSeed {
	var info = rt.FunctionInfo {
		Symbol:   functionNameToSymbol(f.Name),
		Location: f.Location,
	}
	var stat = &Statistics {
		GlobalStatistics: g,
		Config: StatisticsConfiguration {
			FuncKey: info.Symbol,
		},
	}
	switch body := f.Body.(type) {
	case ch.OrdinaryBody:
		return compileOrdinaryFunction(body, info, stat)
	case ch.NativeBody:
		return &rt.FunctionSeedLibraryNative {
			Id:   body.Id,
			Info: info,
		}
	case ch.PredefinedBody:
		var data = (func() rt.GeneratedNativeFunctionSeed {
			switch value := body.Value.(type) {
			case rt.UiObjectSeed:
				return &value
			case stdlib.AssetFile:
				return &rt.AssetFile { AssetFile: value }
			default:
				panic("unknown predefined function body")
			}
		})()
		return &rt.FunctionSeedGeneratedNative {
			Data: data,
			Info: info,
		}
	case ch.SerializerBody:
		return rt.FunctionSeedDataSerializer {
			Type: body.Type,
			Info: info,
		}
	case ch.DeserializerBody:
		return rt.FunctionSeedDataDeserializer {
			Type: body.Type,
			Info: info,
		}
	case ch.ServiceIdentifierBody:
		return rt.FunctionSeedServiceIdentifier {
			Id:   body.Id,
			Info: info,
		}
	default:
		panic("impossible branch")
	}
}

func compileOrdinaryFunction (
	body  ch.OrdinaryBody,
	info  rt.FunctionInfo,
	stat  *Statistics,
) *rt.FunctionSeedUsual {
	if body.Lambda.Self != nil { panic("something went wrong") }
	var ctx = MakeFunctionExprContext(body.Lambda.In, body.Inject, stat)
	compileExpr(body.Lambda.Out, ctx)
	var seed, captures = ctx.CollectEverything(info)
	if len(captures) > 0 {
		panic("unresolved bindings occurred in " + info.Symbol.String())
	}
	return seed
}

func typeNameToSymbol(n name.TypeName) rt.Symbol {
	return rt.MakeSymbol(n.ModuleName, n.ItemName)
}

func functionNameToSymbol(n name.FunctionName) rt.Symbol {
	return rt.MakeSymbol3(n.ModuleName, n.ItemName, n.Tag)
}


